package com.nulldev.util.concurrency.threadIt.v4.utils.impls;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.ICPULayout;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.ICPULayout.CPUCoreArrangement;
import com.nulldev.util.data.Variables;
import com.nulldev.util.io.file.FileUtil;
import com.nulldev.util.osAPI.OS;

public class CPULayoutFetcher {

	public static ICPULayout getFallbackLayout() {
		return new ICPULayout() {

			@Override
			public int getTotalThreadCount() {
				return Runtime.getRuntime().availableProcessors();
			}

			@Override
			public int getTotalCoreCount() {
				return Runtime.getRuntime().availableProcessors();
			}

			@Override
			public CPUCoreArrangement getCoreArrangement() {
				return CPUCoreArrangement.STANDARD;
			}

			@Override
			public int coresIn(final CPUCoreArrangement arrangement) {
				switch (arrangement) {
					case STANDARD:
						return Runtime.getRuntime().availableProcessors();
					default:
						return -1;
				}
			}
		};
	}

	private static ICPULayout linux_get_layout(final int cpu_nr) throws Exception {
		if (cpu_nr <= 1) {
			final File f = new File("/sys/devices/system/cpu/");
			Variables.checkArgument(f.exists());
			Variables.checkArgument(f.isDirectory());
			final List<String> files = ArrayUtils.toArrayList(f.list());
			final Object[] cpus = files.stream().filter((e) -> {
				return e.startsWith("cpu") && Character.isDigit(e.charAt(e.length() - 1));
			}).toArray();
			final int totalThreadCount = cpus.length;
			final Map<String, Integer> i = new HashMap<String, Integer>(totalThreadCount);
			for (final Object _cpu : cpus) {
				final String cpu = (String) _cpu;
				final File ndr = new File("/sys/devices/system/cpu/" + cpu + "/");
				Variables.checkArgument(ndr.exists());
				Variables.checkArgument(ndr.isDirectory());
				if (ArrayUtils.contains(ndr.list(), "topology")) {
					final File tsl = new File("/sys/devices/system/cpu/" + cpu + "/topology/thread_siblings_list");
					Variables.checkArgument(tsl.exists());
					final String tslo = FileUtil.readFirstLine(tsl);
					if (!i.containsKey(tslo)) {
						i.put(tslo, 0);
					} else {
						i.compute(tslo, (k, v) -> {
							return v + 1;
						});
					}
				}
			}
			final int totalCoreCount = i.size();
			CPUCoreArrangement _c_arrangement = CPUCoreArrangement.STANDARD;
			if (totalCoreCount != totalThreadCount)
				_c_arrangement = CPUCoreArrangement.STANDARD_HT;

			final CPUCoreArrangement c_arrangement = _c_arrangement;
			return new ICPULayout() {

				@Override
				public CPUCoreArrangement getCoreArrangement() {
					return c_arrangement;
				}

				@Override
				public int coresIn(final CPUCoreArrangement arrangement) {
					if (arrangement == c_arrangement) {
						return totalCoreCount;
					} else
						return -1;
				}

				@Override
				public int getTotalCoreCount() {
					return totalCoreCount;
				}

				@Override
				public int getTotalThreadCount() {
					return totalThreadCount;
				}

			};
		}
		return getFallbackLayout();
	}

	public static ICPULayout getLayout(final int cpu_nr) {
		if (cpu_nr < 0) {
			return getFallbackLayout();
		} else {
			switch (OS.get()) {
				case Linux:
					try {
						return linux_get_layout(cpu_nr);
					} catch (Exception e) {
						JVM.errorln(e);
						return getFallbackLayout();
					}
				default:
					return getFallbackLayout();
			}
		}
	}

	public static void main(String[] args) {
		final ICPULayout cpuLayout = getLayout(0);
		JVM.println(cpuLayout);
		JVM.println("totalThreadCount: " + cpuLayout.getTotalThreadCount());
		JVM.println("totalCoreCount: " + cpuLayout.getTotalCoreCount());
		JVM.println("coreArrangement: " + cpuLayout.getCoreArrangement());
	}
}
